/*
 * loadbalancer.java
 * This file implement the load balance algorithms
 * The first algorithm maintain appserver table and connection table and use this table info to 
 * 		pick the lightest overhead app server to handle the request; the load balancer also 
 * 		serves as the interface to modify the underlying database
 * The second algorithm picks the candidate using preconfigured info like app server capacity;
 * 		It doesn't maintain connection info, and depend on the statistical law that large number
 * 		of requests will lead to balanced load. and it is more appropriate for application that
 * 		doesn't start a process on each request.
 * 
 * class AgentWorker provides a common interface representing each single agent, even different
 * algorithms and methodology are taken. so AgentWorker has many parameters defined
 * 
 * */

package wps.servicebus.control;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import wps.servicebus.Config;
import wps.servicebus.Config.Agent;
import wps.servicebus.agent.AgentApp;

public class LoadBalancer {
	private ArrayList<AgentWorker> agents = null;	
	AppServerTable appServerTable = null;
	ConnectionTable connectionTable = null;
	
	public LoadBalancer(ArrayList<AgentWorker> agents){
		this.setAgents(agents);
	}
	
	/*
	 * ifTest=true: the load balancer will boot all the agentworker; else 
	 * it will not try booting the agent worker
	 * if reset=true : the load balancer will erase whatever infomation on
	 * the connectio and appserver table and use the config file as the 
	 * configuration
	 * 
	 * */ 
	public LoadBalancer(Config config, boolean reset, boolean isTest){
		if(reset){
			appServerTable = new AppServerTable();
			appServerTable.deleteAllAppServer();
			for(Config.Agent agent : config.getAgents()){
				if(isTest){
					int port = agent.getPort();
					AgentApp inst = new AgentApp();
					inst.Init(port);
				}				
				appServerTable.addAppServerInfo(convert2AppServerInfo(agent, 0));
			}
			connectionTable = new ConnectionTable();
			connectionTable.deleteAllConnectionInfo();
		}

	}
	
	private AppServerInfo convert2AppServerInfo(Agent agent, int m_nInstance) {
		AppServerInfo app = new AppServerInfo();
		app.setM_addrServ(agent.getAddr());
		app.setM_portAgent(agent.getPort());
		app.setM_nInstance(m_nInstance);
		return app;
	}

	public LoadBalancer(ArrayList<AppServerInfo> appServers, boolean reset){
		if(reset){			
			appServerTable = new AppServerTable();
			appServerTable.deleteAllAppServer();
			for(AppServerInfo appServer : appServers){
				appServerTable.addAppServerInfo(appServer);
			}
			connectionTable = new ConnectionTable();
			connectionTable.deleteAllConnectionInfo();
		}
	}
	
	//load balancer according to request number and agent capacity
	public AgentWorker agentBalancerPreRequest(){
		float totalFactor = 0.0f;
		AgentWorker candidate = null;		
		Iterator<AgentWorker> it = getAgents().iterator();		
		while(it.hasNext()){
			AgentWorker cur = (AgentWorker)it.next();
			cur.lbstatus += cur.lbfactor;
			totalFactor += cur.lbfactor;
			if(candidate == null || cur.lbstatus > candidate.lbstatus){
				candidate = cur;
			}
		}		
		candidate.lbstatus -= totalFactor;
		return candidate;
	}
	
	public AgentWorker agentBalancerPreServerInfo(){
		AppServerInfo appserver = appServerTable.topByInstNumber();
		return new AgentWorker(appserver.getM_addrServ(), appserver.getM_portAgent(), appserver.getM_nInstance());
	}
	
	public void incrementInstNumber(AppServerInfo appServer){
		appServerTable.updateAppServer_nInstance(appServer.getM_addrServ(), appServer.getM_portAgent(), 1);
	}
	
	public void decreadeInstNumber(AppServerInfo appServer){
		appServerTable.updateAppServer_nInstance(appServer.getM_addrServ(), appServer.getM_portAgent(), -1);
	}

	public void startConnection(String addr, int agent_port, int app_port, int processID) {
//		System.out.format("%s %d %d %d\n", addr, agent_port, app_port, processID);
		connectionTable.startConnectionInfo(addr, app_port);
		appServerTable.updateAppServer_nInstance(addr, agent_port, 1);
	}

	public void destroyConnection(String addr, int agent_port, int app_port, int processID) {
		connectionTable.destroyConnectionInfo(addr, app_port);
		appServerTable.updateAppServer_nInstance(addr, agent_port, -1);		
	}

	public void setAgents(ArrayList<AgentWorker> agents) {
		this.agents = agents;
	}

	public ArrayList<AgentWorker> getAgents() {
//		return agents;
		ArrayList<AppServerInfo> appServers = appServerTable.readAppServers();
		ArrayList<AgentWorker> agentWorkers = new ArrayList<AgentWorker>();
		for(AppServerInfo appServer : appServers){
			AgentWorker agentWorker = new AgentWorker(appServer.getM_addrServ(), appServer.getM_portAgent(), appServer.getM_nInstance());
			agentWorkers.add(agentWorker);
		}
		return agentWorkers;
	}

	public void printAppServerTable() {		
		System.out.println("\n========AppServer Table========");
		ArrayList<AppServerInfo> appServers = appServerTable.readAppServers();
		for(AppServerInfo appServer : appServers){
			System.out.format("%-20s%-8d%-8d\n", appServer.getM_addrServ(), appServer.getM_portAgent(), appServer.getM_nInstance());			
		}
		System.out.println("==============End==============");		
	}

	public void addConnection(ConnectionInfo connection) {
		connectionTable.addConnectionInfo(connection);		
	}

	public void printConnectionTable() {
		System.out.println("\n====================Connection Table====================");
		ArrayList<ConnectionInfo> connections = connectionTable.readAllConnections();		
		for(ConnectionInfo connection : connections){
			System.out.format("%-5s%-5s%-5s%-5s%-15s%-8d%-8s%-8s\n"
					, (connection.getM_token() == null ? "null" : connection.getM_token())
					, (connection.get_Key() == null ? "null" : connection.get_Key())
					, (connection.getM_fileUri() == null ? "null" : connection.getM_fileUri())
					, (connection.getM_user() == null ? "null" : connection.getM_user())
					, connection.get_AppAddress()
					, connection.get_AppPort()
					, (connection.getM_pid() == 0 ? "null" : Integer.toString(connection.getM_pid()))
					, connection.isActive() ? "Active" : "Inactive");			
		}
		System.out.println("============================End=========================");
		
	}
}

class AgentWorker {
	public AgentWorker(){}
	
	public AgentWorker(String addr, int port, int m_nInstance){
		this.addr = addr;
		this.port = port;
		this.m_nInstance = m_nInstance;
	}
	
	public AgentWorker(int port, float lbfactor, float lbstatus){
		this.port = port;
		this.lbfactor = lbfactor;
		this.lbstatus = lbstatus;
	}	
	
	public String addr = "localhost";
	public int port = 0;
	public float lbfactor = 0;
	public float lbstatus = 0;
	public int m_nInstance = 0;
	public String getURL(){
		return "http://" + addr + ":" + port + "/";	
	}
}
